﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using YasharEl.Infrastructure.EntityModel;
using YasharEl.Infrastructure.EntityModel.Specifications;
using NHibernate;
using System.Linq.Expressions;

namespace YasharEl.Infrastructure.PersistenceModel.NHibernate.Specifications
{
    public class QueryOverSpecificationResult<TEntity, TIdentity> : ISpecificationResult<TEntity, TIdentity>
        where TEntity : class, IGenericEntity<TIdentity>
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        public QueryOverSpecificationResult(IQueryOver<TEntity, TEntity> queryOver)
        {
            this.QueryOver = queryOver;
        }

        /// <summary>
        /// Gets criteria specification.
        /// </summary>
        protected IQueryOver<TEntity, TEntity> QueryOver { get; private set; }


        #region Implementation of ISpecificationResult<TEntity>

        /// <summary>
        /// Takes given count of domain objects.
        /// </summary>
        public ISpecificationResult<TEntity, TIdentity> Take(int count)
        {
            this.QueryOver.Take(count);
            return this;
        }

        /// <summary>
        /// Bypasses a specified number of elements in a sequence and then returns 
        /// the remaining elements.
        /// </summary>
        /// <param name="count">The number of elements to skip before returning 
        /// the remaining elements.</param>
        public ISpecificationResult<TEntity, TIdentity> Skip(int count)
        {
            this.QueryOver.Skip(count);
            return this;
        }



        /// <summary>
        /// Validates given body of the expression for ordering functionality.
        /// The client has to specify a property or field of the entity by which 
        /// the ordering shall happen.
        /// </summary>
        private void ValidateForMemberExpression(System.Linq.Expressions.Expression expression)
        {
            if (!(expression is MemberExpression))
            {
                throw new PersistenceException("A property of the entity needs to be specified.");
            }
        }

        /// <summary>
        /// Sorts the elements of a sequence in ascending order according to a key.
        /// </summary>
        /// <typeparam name="TKey">The type of the key returned by the function that is 
        /// represented by keySelector.</typeparam>
        /// <param name="keySelector">A function to extract a key from an element.</param>
        public ISpecificationResult<TEntity, TIdentity> OrderByAscending<TKey>(Expression<Func<TEntity, TKey>> keySelector)
        {

            //ValidateForMemberExpression(keySelector.Body);
            //var propertyName = (keySelector.Body as MemberExpression).Member.Name;
            //this.QueryOver.OrderBy(keySelector).Asc;
            //this.QueryOver.OrderBy(keySelector.Body).Asc;

            //this.QueryOver.OrderBy(keySelector.).Asc;
            Expression converted = Expression.Convert(keySelector.Body, typeof(object));
            // Use Expression.Lambda to get back to strong typing 
            var keySelector2 = Expression.Lambda<Func<TEntity, object>>(converted, keySelector.Parameters);
            this.QueryOver = this.QueryOver.OrderBy(keySelector2).Asc;
            return this;
        }

        /// <summary>
        /// Sorts the elements of a sequence in descending order according to a key.
        /// </summary>
        /// <typeparam name="TKey">The type of the key returned by the function that is 
        /// represented by keySelector.</typeparam>
        /// <param name="keySelector">A function to extract a key from an element.</param>
        public ISpecificationResult<TEntity, TIdentity> OrderByDescending<TKey>(Expression<Func<TEntity, TKey>> keySelector)
        {
            // Add the boxing operation, but get a weakly typed expression 
            Expression converted = Expression.Convert(keySelector.Body, typeof(object));
            // Use Expression.Lambda to get back to strong typing 
            var keySelector2 = Expression.Lambda<Func<TEntity, object>>(converted, keySelector.Parameters);
            this.QueryOver = this.QueryOver.OrderBy(keySelector2).Desc;

            //ValidateForMemberExpression(keySelector.Body);

            //var propertyName = (keySelector.Body as MemberExpression).Member.Name;
            //this.QueryOver.OrderBy(x => x.Id).Desc;

            return this;
        }

        /// <summary>
        /// Gets the list of domain objects the specification represents.
        /// </summary>
        public IList<TEntity> ToList()
        {
            return this.QueryOver.List();
        }

        /// <summary>
        /// Gets single domain object the specification represents.
        /// </summary>
        public TEntity Single()
        {
            var result = this.QueryOver.SingleOrDefault<TEntity>();

            if (result == null)
            {
                throw new PersistenceException("The input sequence is empty.");
            }

            return result;
        }

        /// <summary>
        /// Returns the only element of a sequence, or a default value if the sequence is empty; 
        /// this method throws an exception if there is more than one element in the sequence.
        /// </summary>
        public TEntity SingleOrDefault()
        {
            return this.QueryOver.SingleOrDefault<TEntity>();
        }

        /// <summary>
        /// Returns the count of domain object the specification represents.; 
        /// </summary>
        public long Count()
        {
            return this.QueryOver.RowCountInt64();
        }

        #endregion
    }
}
